Skip to content

Add webhook endpoints#108

Open
IgorDobryn wants to merge 1 commit intomainfrom
MT-21830-webhooks
Open

Add webhook endpoints#108
IgorDobryn wants to merge 1 commit intomainfrom
MT-21830-webhooks

Conversation

@IgorDobryn
Copy link
Copy Markdown
Contributor

@IgorDobryn IgorDobryn commented May 5, 2026

Motivation

Changes

Webhooks — email-sending.openapi.yml (pending mailtrap/mailtrap-openapi#28)

  • GET /api/accounts/{account_id}/webhooks
  • POST /api/accounts/{account_id}/webhooks
  • GET /api/accounts/{account_id}/webhooks/{webhook_id}
  • PATCH /api/accounts/{account_id}/webhooks/{webhook_id}
  • DELETE /api/accounts/{account_id}/webhooks/{webhook_id}

Summary by CodeRabbit

  • New Features

    • Webhooks API support with full CRUD operations (create, get, list, update, delete) for account webhooks management.
    • New example script demonstrating end-to-end Webhooks API usage.
  • Documentation

    • Updated README to include the Webhooks API example.
  • Tests

    • Added comprehensive test suite with recorded API interactions for all Webhooks API operations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

This PR adds a complete Webhooks API implementation to the Mailtrap Ruby client, including a new Webhook data model, WebhooksAPI class with CRUD operations (list, get, create, update, delete), comprehensive test coverage with VCR fixtures, and usage examples.

Changes

Webhooks API Feature

Layer / File(s) Summary
Data Shape
lib/mailtrap/webhook.rb
Introduces Mailtrap::Webhook as a keyword-initialized Struct with fields for webhook metadata (id, url, active, webhook_type, payload_format, sending_stream, domain_id, event_types, signing_secret).
Core API Implementation
lib/mailtrap/webhooks_api.rb
Implements Mailtrap::WebhooksAPI with public methods list, get, create, update, and delete that delegate to base HTTP helpers. Configures supported options and response mapping to Webhook entities. Wraps request payloads under { webhook: options } and translates responses accordingly.
Library Integration
lib/mailtrap.rb
Adds require_relative 'mailtrap/webhooks_api' to load the new webhooks API module.
Usage Example
examples/webhooks_api.rb
Demonstrates end-to-end workflows: creating email_sending and audit_log webhooks, listing all webhooks, fetching single webhooks, updating webhook state (deactivation and event type changes), and deleting webhooks.
Test Data Model
spec/mailtrap/webhook_spec.rb
Adds RSpec tests verifying Webhook struct initialization and attribute mapping.
Test API Operations
spec/mailtrap/webhooks_api_spec.rb
Comprehensive VCR-tagged test suite covering successful CRUD operations with Webhook mapping, plus error scenarios: incorrect API tokens, not-found webhooks, invalid options, and API validation errors.
Test Fixtures
spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/*
Recorded HTTP interactions for list (success, auth error), get (success, not found), create (success, validation error), update (success, not found), and delete (success, not found) operations.
Documentation & Configuration
README.md, spec/spec_helper.rb
README adds webhooks_api.rb to the examples list. spec_helper.rb adds VCR redaction for signing_secret in response bodies.

Sequence Diagram

sequenceDiagram
    participant Client
    participant WebhooksAPI
    participant HTTP
    participant MailtrapAPI as Mailtrap API
    participant Response

    Client->>WebhooksAPI: create(url, webhook_type, ...)
    WebhooksAPI->>WebhooksAPI: wrap options in { webhook: ... }
    WebhooksAPI->>HTTP: POST /api/accounts/{id}/webhooks
    HTTP->>MailtrapAPI: request with JSON body
    MailtrapAPI->>HTTP: 200 OK + webhook JSON
    HTTP->>WebhooksAPI: response body
    WebhooksAPI->>Response: map to Webhook entity
    Response-->>Client: Webhook instance

    Client->>WebhooksAPI: list()
    WebhooksAPI->>HTTP: GET /api/accounts/{id}/webhooks
    HTTP->>MailtrapAPI: request
    MailtrapAPI->>HTTP: 200 OK + data array
    HTTP->>WebhooksAPI: response body
    WebhooksAPI->>Response: map each item to Webhook
    Response-->>Client: Array<Webhook>

    Client->>WebhooksAPI: delete(webhook_id)
    WebhooksAPI->>HTTP: DELETE /api/accounts/{id}/webhooks/{id}
    HTTP->>MailtrapAPI: request
    MailtrapAPI->>HTTP: 200 OK + deleted webhook JSON
    HTTP->>WebhooksAPI: response body
    WebhooksAPI->>Response: convert to Webhook or return nil
    Response-->>Client: Webhook or nil
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • i7an
  • DagonWat
  • mklocek

Poem

🐰 Webhooks are bouncing into place,
With Ruby grace and HTTP pace,
Create, list, update, delete with care,
Mailtrap's events floating through the air! 📨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description partially follows the template structure but is incomplete. The Motivation section is empty, and the Changes section lists endpoints without explaining implementation details or context. The 'How to test' and 'Images and GIFs' sections are entirely missing. Add content to the Motivation section explaining why webhook endpoints were needed. Expand the Changes section with details about new classes/methods. Include testing instructions and remove or fill the Images and GIFs section.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add webhook endpoints' accurately reflects the main change in the PR, which adds comprehensive webhook API functionality to the Mailtrap Ruby client.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MT-21830-webhooks

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@IgorDobryn IgorDobryn marked this pull request as ready for review May 5, 2026 06:45
@IgorDobryn IgorDobryn requested review from DagonWat, mklocek and piobeny May 5, 2026 06:45
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/mailtrap/webhooks_api.rb`:
- Around line 52-62: The update method currently allows :url—remove :url from
the whitelist passed to base_update in the update(webhook_id, options) method
(so only %i[active payload_format event_types] remain) and update the YARD docs
for `#update` to remove the :url option; also update the test fixture file
maps_response_data_to_Webhook_object.yml to only include supported updatable
fields (remove any url entries) to match the contract.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/maps_response_data_to_Webhook_object.yml`:
- Around line 69-71: The recorded cassette contains a plaintext secret under the
key "signing_secret" in the response body string; update the fixture used by the
test (the YAML cassette that maps response data to Webhook objects) to replace
the actual secret value with a sanitized placeholder like
"<REDACTED_SIGNING_SECRET>" and adjust any expectations in specs to assert
presence or format rather than exact secret value; ensure the change targets the
"signing_secret" entry inside the body string in the cassette so tests still
validate webhook mapping without storing sensitive data.

In `@spec/spec_helper.rb`:
- Line 28: The current masking call interaction.response.body.gsub! only matches
compact JSON and misses formatted variants; update the gsub! invocation that
targets "signing_secret" to use a regex that allows optional whitespace around
the colon (e.g. match "signing_secret"\s*:\s*"...") so both compact and
pretty-printed responses are redacted; keep the same replacement string
("signing_secret":"my-signing-secret") so fixtures remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 438cad09-4460-4fcb-93e3-b76c6aff1696

📥 Commits

Reviewing files that changed from the base of the PR and between f9a59f9 and d60b2e3.

📒 Files selected for processing (18)
  • README.md
  • examples/webhooks_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/webhook.rb
  • lib/mailtrap/webhooks_api.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/maps_response_data_to_Webhook_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_delete/returns_deleted_Webhook.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_delete/when_webhook_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/maps_response_data_to_Webhook_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/when_webhook_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_list/maps_response_data_to_Webhook_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_update/maps_response_data_to_Webhook_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_update/when_webhook_does_not_exist/raises_not_found_error.yml
  • spec/mailtrap/webhook_spec.rb
  • spec/mailtrap/webhooks_api_spec.rb
  • spec/spec_helper.rb

Comment on lines +52 to +62
# @option options [String] :url The URL that will receive webhook payloads
# @option options [Boolean] :active Whether the webhook is active
# @option options [String] :payload_format Payload format (`json` or `jsonlines`)
# @option options [Array<String>] :event_types Event types to subscribe to.
# Applicable only for `email_sending` webhooks.
# @return [Webhook] Updated webhook
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def update(webhook_id, options)
base_update(webhook_id, options, %i[url active payload_format event_types])
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Disallow :url in update option whitelist.

Line 61 currently permits :url, but this update flow should restrict to truly updatable fields. Please remove :url from both the whitelist and YARD docs for #update.

✅ Suggested contract-aligned patch
-    # `@option` options [String] :url The URL that will receive webhook payloads
     # `@option` options [Boolean] :active Whether the webhook is active
     # `@option` options [String] :payload_format Payload format (`json` or `jsonlines`)
     # `@option` options [Array<String>] :event_types Event types to subscribe to.
     #   Applicable only for `email_sending` webhooks.
@@
-      base_update(webhook_id, options, %i[url active payload_format event_types])
+      base_update(webhook_id, options, %i[active payload_format event_types])
As per coding guidelines, `spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_update/maps_response_data_to_Webhook_object.yml` should contain only supported updatable fields (example explicitly notes not `url`).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mailtrap/webhooks_api.rb` around lines 52 - 62, The update method
currently allows :url—remove :url from the whitelist passed to base_update in
the update(webhook_id, options) method (so only %i[active payload_format
event_types] remain) and update the YARD docs for `#update` to remove the :url
option; also update the test fixture file
maps_response_data_to_Webhook_object.yml to only include supported updatable
fields (remove any url entries) to match the contract.

Comment on lines +69 to +71
body:
encoding: ASCII-8BIT
string: '{"data":{"signing_secret":"my-signing-secret","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact signing_secret in recorded response bodies.

Line 71 stores a secret-like value directly in the cassette. Please replace it with a sanitized placeholder and rely on presence-based assertions in specs.

🔒 Suggested fixture hardening
-      string: '{"data":{"signing_secret":"my-signing-secret","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}'
+      string: '{"data":{"signing_secret":"<SIGNING_SECRET>","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}'
As per coding guidelines, VCR cassette fixtures must be checked for potentially sensitive recorded data.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
body:
encoding: ASCII-8BIT
string: '{"data":{"signing_secret":"my-signing-secret","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}'
body:
encoding: ASCII-8BIT
string: '{"data":{"signing_secret":"<SIGNING_SECRET>","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/maps_response_data_to_Webhook_object.yml`
around lines 69 - 71, The recorded cassette contains a plaintext secret under
the key "signing_secret" in the response body string; update the fixture used by
the test (the YAML cassette that maps response data to Webhook objects) to
replace the actual secret value with a sanitized placeholder like
"<REDACTED_SIGNING_SECRET>" and adjust any expectations in specs to assert
presence or format rather than exact secret value; ensure the change targets the
"signing_secret" entry inside the body string in the cassette so tests still
validate webhook mapping without storing sensitive data.

Comment thread spec/spec_helper.rb
interaction.request.uri.gsub!(%r{/organizations/\d+/}, '/organizations/2222222/')
interaction.response.body.gsub!(/"account_id":\d+/, '"account_id": 1111111')
interaction.response.body.gsub!(/"organization_id":\d+/, '"account_id": 2222222')
interaction.response.body.gsub!(/"signing_secret":"[^"]*"/, '"signing_secret":"my-signing-secret"')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Broaden signing_secret masking to handle whitespace variants.

Line 28 only redacts compact JSON. A formatted response like "signing_secret": "..." would bypass masking and leak into fixtures.

Suggested patch
-    interaction.response.body.gsub!(/"signing_secret":"[^"]*"/, '"signing_secret":"my-signing-secret"')
+    interaction.response.body.gsub!(
+      /"signing_secret"\s*:\s*"[^"]*"/,
+      '"signing_secret":"my-signing-secret"'
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interaction.response.body.gsub!(/"signing_secret":"[^"]*"/, '"signing_secret":"my-signing-secret"')
interaction.response.body.gsub!(
/"signing_secret"\s*:\s*"[^"]*"/,
'"signing_secret":"my-signing-secret"'
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/spec_helper.rb` at line 28, The current masking call
interaction.response.body.gsub! only matches compact JSON and misses formatted
variants; update the gsub! invocation that targets "signing_secret" to use a
regex that allows optional whitespace around the colon (e.g. match
"signing_secret"\s*:\s*"...") so both compact and pretty-printed responses are
redacted; keep the same replacement string
("signing_secret":"my-signing-secret") so fixtures remain consistent.

Comment thread examples/webhooks_api.rb
Comment on lines +19 to +22
webhooks_api.create(
url: 'https://example.com/mailtrap/audit-log',
webhook_type: 'audit_log'
)
Copy link
Copy Markdown
Contributor

@mklocek mklocek May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw. now that we have Webhook API, we could also add helpers to the SDKs to verify webhooks using the signature. Basically what we have in code samples in https://docs.mailtrap.io/email-api-smtp/advanced/webhooks#verifying-the-signature

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants